# 指定文件编码为UTF-8
# coding: utf-8

"""
batch_norm_gradient_check.py 功能说明：
1. 实现批量归一化(Batch Normalization)的梯度检查功能
2. 比较数值梯度(numerical gradient)和反向传播梯度(backpropagation gradient)的差异
3. 使用MNIST数据集中的单个样本进行测试
4. 输出各层参数梯度的平均绝对误差

关键点：
- 验证批量归一化层的反向传播实现是否正确
- 数值梯度计算准确但计算量大，用于验证反向传播的正确性
- 反向传播梯度计算高效，用于实际训练
- 两种方法计算的梯度应该非常接近
- 差异值可以反映反向传播实现是否正确
"""

# 导入系统模块和操作系统模块
import sys, os
# 添加父目录到系统路径，以便导入父目录中的模块
sys.path.append(os.pardir)

# 导入NumPy数值计算库
import numpy as np
# 从dataset.mnist模块导入MNIST数据加载函数
from dataset.mnist import load_mnist
# 从common.multi_layer_net_extend模块导入MultiLayerNetExtend类
from common.multi_layer_net_extend import MultiLayerNetExtend

# 加载MNIST数据集
# normalize=True: 对图像进行归一化处理(0~1范围)
# one_hot_label=True: 标签采用one-hot编码
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, one_hot_label=True)

# 初始化多层神经网络(带批量归一化)
# input_size=784: 输入层784个神经元(28x28像素)
# hidden_size_list=[100,100]: 两个隐藏层，每层100个神经元
# output_size=10: 输出层10个神经元(对应0-9数字分类)
# use_batchnorm=True: 使用批量归一化
network = MultiLayerNetExtend(input_size=784, hidden_size_list=[100, 100], output_size=10,
                              use_batchnorm=True)

# 取训练集第一个样本作为测试数据
x_batch = x_train[:1]
t_batch = t_train[:1]

# 计算反向传播梯度(高效实现)
grad_backprop = network.gradient(x_batch, t_batch)
# 计算数值梯度(精确但计算量大)
grad_numerical = network.numerical_gradient(x_batch, t_batch)

# 比较两种梯度计算方法的差异
for key in grad_numerical.keys():
    # 计算平均绝对误差
    diff = np.average(np.abs(grad_backprop[key] - grad_numerical[key]))
    # 打印各层参数的梯度差异
    print(key + ":" + str(diff))

"""
预期输出分析：
W1: 很小的值(如1e-7量级)
b1: 很小的值(如1e-7量级)
W2: 很小的值(如1e-7量级)
b2: 很小的值(如1e-7量级)
gamma1: 很小的值(如1e-7量级)
beta1: 很小的值(如1e-7量级)

说明：
1. 如果差异值都很小(如小于1e-7)，说明反向传播实现正确
2. 如果批量归一化相关参数(gamma,beta)的差异值较大，说明批量归一化的反向传播实现可能有问题
3. 数值梯度是准确的参考值，但计算成本高
4. 反向传播梯度应该与数值梯度非常接近
"""

"""
代码执行流程：
1. 加载并预处理MNIST数据集
2. 初始化带批量归一化的多层神经网络
3. 选取单个训练样本作为测试数据
4. 分别用两种方法计算梯度：
   - numerical_gradient: 使用数值方法计算梯度(准确但慢)
   - gradient: 使用反向传播计算梯度(高效)
5. 比较两种方法得到的梯度差异
6. 输出各层参数的梯度平均绝对误差

注意事项：
1. 该脚本主要用于调试和验证反向传播实现
2. 实际训练时应使用反向传播梯度
3. 批量归一化的梯度计算比普通层更复杂
4. 如果差异过大，需要检查批量归一化的实现代码
"""
